/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "zobjects_network.h"

int ZlangWaitforTcp( struct ZlangRuntime *rt , short event , int fd , int32_t *timeout , int32_t *elapse )
{
#if defined(__linux__)
	struct pollfd		pfd ;
	struct timeval		begin_timestamp ;
	struct timeval		end_timestamp ;
	int			nret = 0 ;
	
	pfd.fd = fd ;
	pfd.events = event|POLLERR|POLLHUP ;
	if( (*timeout) == -1 )
	{
		nret = poll( & pfd , 1 , -1 ) ;
		if( nret < 0 )
			return ZLANG_TCP_ERROR;
		else
			return nret;
	}
	else
	{
		gettimeofday( & begin_timestamp , NULL );
		nret = poll( & pfd , 1 , (int)(*timeout) ) ;
		gettimeofday( & end_timestamp , NULL );
		DIFF_TIMEVAL_TO_MILLISECONDS( begin_timestamp , end_timestamp , (*timeout) , (*elapse) )
		if( nret < 0 )
			return ZLANG_TCP_ERROR;
		else if( nret == 0 )
			return ZLANG_TCP_TIMEOUT;
		else
			return nret;
	}
#elif defined(_WIN32)
	fd_set			fds ;
	struct timeval		t ;
	struct timeval		begin_timestamp ;
	struct timeval		end_timestamp ;
	int			nret = 0 ;
	
	FD_ZERO( & fds );
	FD_SET( fd , & fds );
	if( (*timeout) == -1 )
	{
		if( event == POLLIN )
			nret = select( 1 , & fds , NULL , NULL , NULL ) ;
		else if( event == POLLOUT )
			nret = select( 1 , NULL , & fds , NULL , NULL ) ;
		else if( event == POLLERR )
			nret = select( 1 , NULL , NULL , & fds , NULL ) ;
		if( nret < 0 )
			return ZLANG_TCP_ERROR;
		else
			return nret;
	}
	else
	{
		gettimeofday( & begin_timestamp , NULL );
		t.tv_sec = (*timeout)/1000 ;
		t.tv_usec = (*timeout)%1000*1000 ;
		if( event == POLLIN )
			nret = select( 1 , & fds , NULL , NULL , & t ) ;
		else if( event == POLLOUT )
			nret = select( 1 , NULL , & fds , NULL , & t ) ;
		else if( event == POLLERR )
			nret = select( 1 , NULL , NULL , & fds , & t ) ;
		gettimeofday( & end_timestamp , NULL );
		DIFF_TIMEVAL_TO_MILLISECONDS( begin_timestamp , end_timestamp , (*timeout) , (*elapse) )
		if( nret < 0 )
			return ZLANG_TCP_ERROR;
		else if( nret == 0 )
			return ZLANG_TCP_TIMEOUT;
		else
			return nret;
	}
#endif
}

int32_t ZlangSendTcp( struct ZlangRuntime *rt , int sock , char *buf , int32_t write_len , int32_t *timeout , int32_t *elapse )
{
	int32_t		len ;
	char		*p = NULL ;
	int32_t		wrote_len ;
	int32_t		remain_len ;
	
	int		nret = 0 ;
	
	p = buf ;
	wrote_len = 0 ;
	remain_len = write_len ;
	for( ; ; )
	{
		nret = ZlangWaitforTcp( rt , POLLOUT , sock , timeout , elapse ) ;
		if( nret < 0 )
			return nret;
		
		len = SEND( sock , p , remain_len , 0 ) ;
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "[%d] = send( [%d] , [%.*s] , [%d] ) ; errno[%d]" , len , sock , remain_len,p , remain_len , errno )
		if( len < 0 )
			return ZLANG_TCP_ERROR;
		
		p += len ;
		wrote_len += len ;
		remain_len -= len ;
		
		if( remain_len == 0 )
			break;
	}
	
	return wrote_len;
}

int32_t ZlangReceiveTcp( struct ZlangRuntime *rt , int sock , char *buf , int32_t read_len , int32_t *timeout , int32_t *elapse )
{
	int32_t		len ;
	char		*p = NULL ;
	int32_t		readd_len ;
	int32_t		remain_len ;
	
	int		nret = 0 ;
	
	p = buf ;
	readd_len = 0 ;
	remain_len = read_len ;
	for( ; ; )
	{
		nret = ZlangWaitforTcp( rt , POLLIN , sock , timeout , elapse ) ;
		if( nret < 0 )
			return nret;
		
		len = RECV( sock , p , remain_len , 0 ) ;
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "[%d] = recv( [%d] , [%.*s] , [%d] ) ; errno[%d]" , len , sock , len,p , remain_len , errno )
		if( len < 0 )
			return ZLANG_TCP_ERROR;
		else if( len == 0 )
			return ZLANG_TCP_DISCONNECTED_FROM_PEER;
		
		p += len ;
		readd_len += len ;
		remain_len -= len ;
		
		if( remain_len == 0 )
			break;
	}
	
	return readd_len;
}

void ZlangSetConnectingTimeout( struct ZlangRuntime *rt , struct ZlangTcp *tcp , int32_t connecting_timeout )
{
	tcp->connecting_timeout = connecting_timeout ;
	return;
}

int32_t ZlangGetConnectingTimeout( struct ZlangRuntime *rt , struct ZlangTcp *tcp )
{
	return tcp->connecting_timeout;
}

int ZlangListenTcp( struct ZlangRuntime *rt , struct ZlangTcp *tcp , char *ip , int32_t port )
{
	struct sockaddr_in		listen_addr ;
	int				nret = 0 ;
	
	if( tcp->sock != INVALID_SOCKET )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "socket already created" )
		return -1;
	}
	
#if defined(__linux__)
	signal( SIGPIPE , SIG_IGN );
#endif
	
	tcp->sock = (int)socket( AF_INET , SOCK_STREAM , IPPROTO_TCP ) ;
	if( tcp->sock == INVALID_SOCKET )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "socket failed , errno[%d]" , errno )
		return -11;
	}
	else
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "socket ok , sock[%d]" , tcp->sock )
	}
	
	{
		int	onoff = 1 ;
		setsockopt( tcp->sock , SOL_SOCKET , SO_REUSEADDR , (void *) & onoff , sizeof(int) );
	}
	
	memset( tcp->ip , 0x00 , sizeof(tcp->ip) );
	strncpy( tcp->ip , ip , sizeof(tcp->ip)-1 );
	tcp->port = port ;
	
	memset( & listen_addr , 0x00 , sizeof(struct sockaddr_in) );
	listen_addr.sin_family = AF_INET ;
	if( STRCMP( ip , == , "*" ) )
		listen_addr.sin_addr.s_addr = INADDR_ANY ;
	else
		inet_aton( tcp->ip , & (listen_addr.sin_addr) );
	listen_addr.sin_port = htons( (unsigned short)(tcp->port) );
	nret = bind( tcp->sock , (struct sockaddr *) & listen_addr , sizeof(struct sockaddr) ) ;
	if( nret == -1 )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "bind[%d][%s:%"PRIi32"] failed , errno[%d]" , tcp->sock , tcp->ip , tcp->port , errno )
		return -12;
	}
	else
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "bind[%d][%s:%"PRIi32"] ok" , tcp->sock , tcp->ip , tcp->port )
	}
	
	nret = listen( tcp->sock , 10240 ) ;
	if( nret == -1 )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "listen[%d][%s:%"PRIi32"] failed , errno[%d]" , tcp->sock , tcp->ip , tcp->port , errno )
		return -13;
	}
	else
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "listen[%d][%s:%"PRIi32"] ok" , tcp->sock , tcp->ip , tcp->port )
	}
	
	return 0;
}

int ZlangAcceptTcp( struct ZlangRuntime *rt , struct ZlangTcp *listen_tcp , struct ZlangTcp *accept_tcp )
{
	struct sockaddr_in	accept_addr ;
	SOCKLEN_T		accept_addr_len ;
	int			accept_sock ;
	
	if( listen_tcp->sock == INVALID_SOCKET )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "socket is not listen" )
		return -1;
	}
	
	TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "accept[%d][%s:%"PRIi32"] ..." , listen_tcp->sock , listen_tcp->ip , listen_tcp->port )
	memset( & accept_addr , 0x00 , sizeof(struct sockaddr_in) );
	accept_addr_len = sizeof(struct sockaddr) ;
	accept_sock = (int)accept( (SOCKET)(listen_tcp->sock) , (struct sockaddr *) & accept_addr , & accept_addr_len );
	if( accept_sock == -1 )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "accept[%d][%s:%"PRIi32"] failed , errno[%d]" , listen_tcp->sock , listen_tcp->ip , listen_tcp->port , errno )
		return -11;
	}
	else
	{
		memset( accept_tcp->ip , 0x00 , sizeof(accept_tcp->ip) );
		strcpy( accept_tcp->ip , inet_ntoa(accept_addr.sin_addr) );
		accept_tcp->port = (int)ntohs( accept_addr.sin_port ) ;
		accept_tcp->sock = accept_sock ;
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "accept[%d][%s:%"PRIi32"] ok , client[%d][%s:%"PRIi32"]" , listen_tcp->sock , listen_tcp->ip , listen_tcp->port , accept_tcp->sock , accept_tcp->ip , accept_tcp->port )
	}
	
	return 0;
}

int ZlangConnectTcp( struct ZlangRuntime *rt , struct ZlangTcp *tcp , char *ip , int32_t port )
{
	struct sockaddr_in		connect_addr ;
	SOCKLEN_T			connect_addr_len ;
	int				error ;
	int				code ;
	int				nret = 0 ;
	
	if( tcp->sock != INVALID_SOCKET )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "socket already created" )
		return -1;
	}
	
#if defined(__linux__)
	signal( SIGPIPE , SIG_IGN );
#endif
	
	tcp->sock = (int)socket( AF_INET , SOCK_STREAM , IPPROTO_TCP ) ;
	if( tcp->sock == INVALID_SOCKET )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "socket failed , errno[%d]" , errno )
		return -11;
	}
	else
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "socket ok , sock[%d]" , tcp->sock )
	}
	
	memset( tcp->ip , 0x00 , sizeof(tcp->ip) );
	strncpy( tcp->ip , ip , sizeof(tcp->ip)-1 );
	tcp->port = port ;
	
	SET_TCP_BLOCK( tcp->sock , 0 )
	
	TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "[%d]connect[%s:%d] ..." , tcp->sock , tcp->ip , tcp->port )
	memset( & connect_addr , 0x00 , sizeof(struct sockaddr_in) );
	connect_addr.sin_family = AF_INET ;
	connect_addr.sin_addr.s_addr = inet_addr(ip) ;
	connect_addr.sin_port = htons( (unsigned short)port );
	nret = connect( tcp->sock , (struct sockaddr *) & connect_addr , sizeof(struct sockaddr) ) ;
	if( nret == -1 )
	{
		if( errno == EINPROGRESS )
		{
			TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "connecting[%d] is progress" , tcp->sock )
			
			tcp->connecting_elapse = 0 ;
			nret = ZlangWaitforTcp( rt , POLLOUT , tcp->sock , & (tcp->connecting_timeout) , & (tcp->connecting_elapse) ) ;
			if( nret == -1 )
			{
				TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "wait for [%d] out on connecting failed , errno[%d]" , tcp->sock , errno )
				ZlangCloseTcp( rt , tcp );
				return -12;
			}
			else if( nret == 0 )
			{
				TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "wait for [%d] timeout on connecting" , tcp->sock )
				ZlangCloseTcp( rt , tcp );
				return -13;
			}
			
			connect_addr_len = sizeof(int) ;
			code = getsockopt( (SOCKET)(tcp->sock) , SOL_SOCKET , SO_ERROR , (void*) & error , & connect_addr_len ) ;
			if( code < 0 || error )
			{
				TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "check connection status on connecting[%d] failed , errno[%d]" , tcp->sock , errno )
				ZlangCloseTcp( rt , tcp );
				return -14;
			}
			
			nret = 0 ;
		}
		
		if( nret == -1 )
		{
			TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "[%d]connect[%s:%"PRIi32"] failed , errno[%d]" , tcp->sock , tcp->ip , tcp->port , errno )
			ZlangCloseTcp( rt , tcp );
			return -15;
		}
	}
	
	TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "[%d]connect[%s:%"PRIi32"] ok" , tcp->sock , tcp->ip , tcp->port )
	
	SET_TCP_BLOCK( tcp->sock , 1 )
	
	return 0;
}

int ZlangCloseTcp( struct ZlangRuntime *rt , struct ZlangTcp *tcp )
{
	if( tcp->sock == INVALID_SOCKET )
	{
		TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "socket is not avarible" )
		return -1;
	}
	
	TEST_RUNTIME_DEBUG_THEN_PRINT( rt , "close sock[%d]" , tcp->sock )
	CLOSESOCKET( tcp->sock ); tcp->sock = SOCKET_INIT ;
	
	return 0;
}

struct FasterSection *GetHtmlSection( struct ZlangDirectProperty_htmlsection *htmlsection )
{
	if( htmlsection == NULL )
		return NULL;
	
	return htmlsection->sec;
}

ZlangImportObjectFunction ZlangImportObject_ip;
ZlangImportObjectFunction ZlangImportObject_tcp;
ZlangImportObjectFunction ZlangImportObject_http;
ZlangImportObjectFunction ZlangImportObject_httpclient;
ZlangImportObjectFunction ZlangImportObject_httpserver;
ZlangImportObjectFunction ZlangImportObject_htmlsection;

#include "charset_GB18030.c"
#include "charset_UTF8.c"

int ZlangImportObjects( struct ZlangRuntime *rt )
{
	struct ZlangObject	*obj = NULL ;
	
	obj = ZlangImportObject_ip( rt ) ;
	if( obj == NULL )
		return 1;
	
	obj = ZlangImportObject_tcp( rt ) ;
	if( obj == NULL )
		return 1;
	
	obj = ZlangImportObject_http( rt ) ;
	if( obj == NULL )
		return 1;
	
	obj = ZlangImportObject_httpclient( rt ) ;
	if( obj == NULL )
		return 1;
	
	obj = ZlangImportObject_httpserver( rt ) ;
	if( obj == NULL )
		return 1;
	
	obj = ZlangImportObject_htmlsection( rt ) ;
	if( obj == NULL )
		return 1;
	
	ImportCharsetAlias( rt , g_zlang_charset_aliases_GB18030 );
	ImportCharsetAlias( rt , g_zlang_charset_aliases_UTF8 );
	
	return 0;
}

